package cn.bonoon.kernel.web.html.form;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.bonoon.handler.impl.StandardAutoManager;
import cn.bonoon.kernel.IdGenerator;
import cn.bonoon.kernel.annotations.QueryExpression;
import cn.bonoon.kernel.descriptors.DescriptorFactory;
import cn.bonoon.kernel.descriptors.ObjectDescriptor;
import cn.bonoon.kernel.descriptors.UIClassDescriptor;
import cn.bonoon.kernel.descriptors.UIPropertyAccessor;
import cn.bonoon.kernel.query.PageCondition;
import cn.bonoon.kernel.query.SearchCondition;
import cn.bonoon.kernel.support.entities.Persistable;
import cn.bonoon.kernel.support.models.AjaxNode;
import cn.bonoon.kernel.util.StringHelper;
import cn.bonoon.kernel.web.annotations.AutoDataLoader;
import cn.bonoon.kernel.web.annotations.components.AsHidden;
import cn.bonoon.kernel.web.annotations.components.AsTabs;
import cn.bonoon.kernel.web.annotations.components.TabHome;
import cn.bonoon.kernel.web.annotations.components.TabItem;
import cn.bonoon.kernel.web.annotations.form.FormGroup;
import cn.bonoon.kernel.web.annotations.form.FormGroupItem;
import cn.bonoon.kernel.web.annotations.form.FormHelper;
import cn.bonoon.kernel.web.annotations.form.FormHelper.Position;
import cn.bonoon.kernel.web.annotations.form.InsertCell;
import cn.bonoon.kernel.web.annotations.form.InsertCells;
import cn.bonoon.kernel.web.annotations.grid.AsDataGrid;
import cn.bonoon.kernel.web.annotations.grid.AsTreeGrid;
import cn.bonoon.kernel.web.annotations.grid.PaginationType;
import cn.bonoon.kernel.web.handlers.loader.AbstractLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.AjaxNodeLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.ItemLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.NodeLoaderHandler;
import cn.bonoon.kernel.web.html.AbstractUIBuilder;
import cn.bonoon.kernel.web.html.InternalHtmlInfo;
import cn.bonoon.kernel.web.html.UIPropertyBuilder;
import cn.bonoon.kernel.web.html.grid.DatagridBuilder;
import cn.bonoon.kernel.web.html.grid.StandardGridBuilder;
import cn.bonoon.kernel.web.html.grid.TreegridBuilder;

/**
 * <pre>
 * V2.2
 *     可以指定表单的css class样式
 *    
 * 对界面进行解释，解释结束应该消除这些类，可以释放内存
 * </pre>
 * @author jackson
 * @version 2.3
 */
public abstract class AbstractFormBuilder extends AbstractUIBuilder implements UIPropertyBuilder, FormBuilder {
	
	protected static final String BINDED_NAME = "form";
	protected static final String DEFAULT_TABLE_CLASS = "editor";
	
	protected final String bindedName;

	protected String tableClass = DEFAULT_TABLE_CLASS;
	
	private final StringBuilder hidden = new StringBuilder();

	protected final StringBuilder initHtml = new StringBuilder();
	/**
	 * 在界面处理结束后再执行的代码块，如：dialog在打开后再执行
	 */
	private StringBuilder initBlock;
	
	/**
	 * 在保存之前处理
	 */
	protected StringBuilder initSubmit;
	private final Object OBJECT_CACHED = new Object();
	private final Map<Class<?>, Object> cached = new HashMap<Class<?>, Object>();
	
	public String getEntityId() {
		return this.id;
	}
	
	@Override
	public boolean hasSameType(Class<?> comType) {
		return cached.put(comType, OBJECT_CACHED) != null;
	}
	
	@Override
	public final StringBuilder getInitSubmit() {
		if(null == initSubmit){
			initSubmit = new StringBuilder();
		}
		return initSubmit;
	}
	
	@Override
	public final StringBuilder getInitBlock() {
		if(null == initBlock){
			initBlock = new StringBuilder();
		}
		return initBlock;
	}
	
	@Override
	public final StringBuilder getInitHtml() {
		return initHtml;
	}

	public void setTableClass(String tableClass){
		this.tableClass = tableClass;
	}
	
	protected AbstractFormBuilder(UIClassDescriptor uiDescriptor, StandardAutoManager manager) {
		super(uiDescriptor, manager, new InternalHtmlInfo(manager.getServletContext()));
		this.bindedName = BINDED_NAME;
		__init();
	}
	
	/**
	 * 内部使用的
	 * @param bindedName
	 * @param descriptor
	 * @param parent
	 */
	protected AbstractFormBuilder(String bindedName, ObjectDescriptor uiDescriptor, AbstractFormBuilder parent) {
		super(uiDescriptor, parent.manager, parent.html);
		this.bindedName = bindedName;
		__init();
	}
	
	protected int column;
	//表单标题头部的宽度
	protected int headWidth;
	//表单内容项(编辑项)的宽度
	protected int propertyWidth;
	//整个表单预计的总的宽度
	protected int width;
	//表单的高度，如果没有指定，则使用默认值
	protected int height;
	/**
	 * 初始化以下字段的默认值，如果子类有特殊的指定，则需要使用子类的指定
	 */
	protected void init(){ }
	
	private void __init(){
		init();
		if(column <= 0){
			column = 1;
		}
		if(headWidth < 1){
			//使用默认值
			headWidth = 100;
		}
		if(propertyWidth < 1){
			//使用默认值
			propertyWidth = 150;
		}
		if(width < 1){
			width = column * (headWidth + propertyWidth) + 2;
		}
	}
	
	@Override
	public int getHeadWidth() {
		return headWidth;
	}
	@Override
	public int getPropertyWidth() {
		return propertyWidth;
	}
	@Override
	public int getColumn() {
		return column;
	}
	
	@Override
	public int getWidth() {
		return width;
	}
	
	@Override
	public int getHeight() {
		return height;
	}
	
	protected void __parseForm(StringBuilder sb){
		sb.append(hidden);
		FormHelper fh = uiDescriptor.getAnnotation(FormHelper.class);
		if(null != fh){
			Position psn = fh.position();
			if(psn == Position.TOP || psn == Position.BOTH){
				__parseHelper(sb, fh);
			}
			sb.append(initHtml);
			if(psn == Position.BOTTOM || psn == Position.BOTH){
				__parseHelper(sb, fh);
			}
		}else{
			sb.append(initHtml);
		}
	}

	protected void __parseHelper(StringBuilder sb, FormHelper fh) {
		switch (fh.type()) {
		case PROPERTY:
			sb.append("<div>$!{").append(fh.value()).append("}</div>");
			break;
		case PARSE:
			sb.append("#parse('").append(fh.value()).append("')");
			break;
		case INCLUDE:
			sb.append("#include('").append(fh.value()).append("')");
			break;
		default:
			sb.append("<div>").append(fh.value()).append("</div>");
			break;
		}
	}
	
	protected abstract PropertyParser readProperty(UIPropertyAccessor accessor);
	
	private interface ICell{
		ICell empty = new ICell(){

			@Override
			public void parseCell(UIPropertyBuilder pb, int column, int index) {
				//nothing to do!
			}
			
		};
		
		void parseCell(UIPropertyBuilder pb, int column, int index)throws Exception;
	}
	
	private class Row{
		private final ICell[] cells = new ICell[column];
		private int index = 0;
		private Row nextRow;
		
		private Row parseRow(UIPropertyBuilder pb)throws Exception{
			pb.getInitHtml().append("<tr>");
			for(int i = 0; i < column; i++){
				if(cells[i] == null){
					throw new RuntimeException("可能是RowSpan>1!");
				}
				cells[i].parseCell(pb, column, i);
			}
			pb.getInitHtml().append("<td></td></tr>");
			return nextRow;
		}
		
		private void fillCell(){
			//最后一行，有可能只有部分cell被定义，后面的cell没被定义的情况
			while(index < column && cells[index] != null){
				index++;
			}
			if(index < column){
				cells[index++] = new ICell(){

					@Override
					public void parseCell(UIPropertyBuilder pb, int column, int index) {
						final int length = column - index;
						pb.getInitHtml().append("<td colspan='").append(length * 2).append("'></td>");
					}
					
				};
				while(index < column){
					cells[index++] = ICell.empty;
				}
			}
		}
		
		private Row addCell(PropertyParser property){
			while(index < column && cells[index] != null){
				index++;
			}
			if(index < column){
				int rowspan = property.rowspan();
				if(rowspan > 0){
					Row row = this;
					int p = index;
					int colspan = property.colspan() + 1;
					for(int i = 0; i < rowspan; i++){
						if(null == row.nextRow){
							row.nextRow = new Row();
						}
						row = row.nextRow;
						for(int j = 0; j < colspan && p < column; j++){
							cells[p++] = ICell.empty;
						}
					}
				}
				cells[index++] = new Cell(property);
				int pp = index;
				int colspan = property.colspan();
				for(int i = 0; i < colspan && pp < column; i++){
					cells[pp++] = ICell.empty;
				}
				return this;
			}
			
			if(null == nextRow){
				nextRow = new Row();
			}
			return nextRow.addCell(property);
		}
	}
	
	private class Cell implements ICell{
		private final PropertyParser property;
		private Cell(PropertyParser property){
			this.property = property;
		}
		@Override
		public void parseCell(UIPropertyBuilder pb, int column, int index) throws Exception{
			property.parseCell(pb, column, index);
		}
	}

	private void addHidden(String id, String name){
		hidden.append("<input type='hidden' id='").append(id);
		hidden.append("' name='").append(name).append("' value='$!{");
		hidden.append(bindedName).append('.').append(name).append("}'/>");
	}
	
	@Override
	public void addHidden(String name) {
		hidden.append("<input type='hidden' id='").append(name);
		hidden.append("' name='").append(name).append("' value='$!{");
		hidden.append(name).append("}'/>");
	}
	
	@Override
	public boolean parse() throws Exception {
		List<PropertyParser> properties = new ArrayList<PropertyParser>(); 
		addHidden(this.id, "id");
		for (UIPropertyAccessor accessor : uiDescriptor.values()) {
			String name = accessor.getName();
			if("id".equals(name)){
				//不处理id的情况
			} else if(accessor.isAnnotationPresent(AsHidden.class)){
				addHidden(name, name);
			}else{
				PropertyParser fp = readProperty(accessor);
				if(null != fp){
					properties.add(fp);
				}
			}
		}
		InsertCell ic = uiDescriptor.getAnnotation(InsertCell.class);
		if(null != ic){
			properties.add(new PropertySimpleParser(this, ic));
		}
		InsertCells ics = uiDescriptor.getAnnotation(InsertCells.class);
		if (null != ics) {
			for (int i = 0; i < ics.value().length; i++) {
				ic = ics.value()[i];
				properties.add(new PropertySimpleParser(this, ic));
			}
		}
		Collections.sort(properties);
		Row first = new Row(), next = first;
		for(PropertyParser fp : properties){
			next = next.addCell(fp);
		}
		next.fillCell();
		next = first;
		FormGroup group = uiDescriptor.getAnnotation(FormGroup.class);
		if(null != group && group.value().length > 0){
			if(group.fieldset()){
				width += 40;
				END : for(FormGroupItem fgi : group.value()){
					int startRow = fgi.startRow();
					if(startRow > 0){
						_startForm();
						for(int start = 0; start < startRow; start++){
							next = next.parseRow(this);
							if(null == next){
								_endForm();
								break END;
							}
						}
						_endForm();
					}
					initHtml.append("<fieldset class='").append(_parseClass(fgi, group)).append("'><legend>").append(fgi.name());
					String gid = _collapsible(fgi, initHtml);
					initHtml.append("</legend>");
					if(null != gid){
						_startForm(gid);
					}else{
						_startForm();
					}
					int rowSize = fgi.length();
					while((rowSize-->0) && (next = next.parseRow(this)) != null);
					_endForm();
					initHtml.append("</fieldset>");
				}
				if(null != next){
					_startForm();
					while((next = next.parseRow(this)) != null);
					_endForm();
				}
			}else{
				_startForm();
				int colsize = column * 2;
				
				END : for(FormGroupItem fgi : group.value()){
					int startRow = fgi.startRow();
					for(int start = 0; start < startRow; start++){
						next = next.parseRow(this);
						if(null == next) break END;
					}
					//开始分组处理

					initHtml.append("<thead><tr><th class='").append(_parseClass(fgi, group)).append("' colspan='").append(colsize).append("'>").append(fgi.name());
					String gid = _collapsible(fgi, initHtml);
					initHtml.append("</th><td></td></tr></thead><tbody");
					if(null != gid){
						initHtml.append(" id='").append(gid).append("'");
					}
					initHtml.append('>');
					int rowSize = fgi.length();
					while((rowSize-->0) && (next = next.parseRow(this)) != null);
					initHtml.append("</tbody>");
				}
				if(null != next){
					while((next = next.parseRow(this)) != null);
				}
				_endForm();
			}
		}else{
			_startForm();
			while((next = next.parseRow(this)) != null);
			_endForm();
		}
		
		//这里处理tab的情况
		AsTabs tabs = uiDescriptor.getAnnotation(AsTabs.class);
		if(null != tabs){
			height = tabs.height();
			width += 8;
			String tid = IdGenerator.fixed();
			TabHome home = uiDescriptor.getAnnotation(TabHome.class);
			if(null != home){
				html.append("<div id='").append(tid).append("'><div title='").append(home.name());
				if(home.closable()){
					html.append("' closable='true");
				}
				html.append("' data-settings=\"");
				if(!home.iconCls().isEmpty()){
					html.append("iconClassname:'").append(home.iconCls()).append("'");
				}
				__parseForm(html.append("\">"));
				html.append("</div>");
				__parseTabItem(html, tabs, tid);
				html.append("</div>");
			}else{
				String lid = IdGenerator.fixed();
				html.block("jQuery('#").append(lid).append("').bupborderlayout();");
				html.append("<div fit='true' id='").append(lid).append("'><div data-settings=\"zone:'center',border:false\">");
				__parseForm(html.getHtml());
				html.append("</div><div data-settings=\"zone:'south',border:false\" ><div fit='true' id='").append(tid).append("'>");
				__parseTabItem(html, tabs, tid);
				html.append("</div></div></div>");
			}
		}else{
			__parseForm(html.getHtml());
		}
		//TODO 组合的时候也应该把操作组合在一起，不然会出现多个相同的方法
//		html.script(onloadFunction)
//			.append("=function(){").append(initBlock)
//			.append("};").append(onsaveFunction)
//			.append("=function(){").append(initSave)
//			.append("};");
		return true;
	}
	
	private String _collapsible(FormGroupItem fgi, StringBuilder initHtml){
		if(fgi.collapsible()){
			String gid = IdGenerator.fixed();
			initHtml.append("<a style='color:gray;' onclick=\"jQuery('#").append(gid).append("').toggle();return false;\" href='#'>[显示/隐藏]</a>");
			return gid;
		}
		return null;
	}
	
	private void _startForm(String id){
		initHtml.append("<table id='").append(id).append("' style='width:100%' class='").append(tableClass).append("'>");
	}

	private void _startForm(){
		initHtml.append("<table style='width:100%;' class='").append(tableClass).append("'>");
	}
	
	private void _endForm(){
		initHtml.append("</table>");
	}
	
	private String _parseClass(FormGroupItem fgi, FormGroup group){
		return fgi.cssClass().isEmpty() ? group.cssClass() : fgi.cssClass();
	}
	
//	private static final int DEFAULT_HEIGHT = 250;
//	private static final int MIN_HEIGHT = 30;

	protected void __parseTabItem(InternalHtmlInfo html, AsTabs tabs, String tid) throws Exception {
		html.block("jQuery('#").append(tid).append("').buptab({tabPosition:").append(tabs.tabPosition());
		if(tabs.headerWidth() > 0){
			html.block(",headerWidth:").append(tabs.headerWidth());
		}
		if(!tabs.tools().isEmpty()){
			html.block(",tools:").append(tabs.tools()).append(",toolPosition:").append(tabs.toolPosition());
		}
		html.block(",fit:true});");
		
		for (TabItem ti : tabs.value()) {
			html.append("<div title='").append(ti.name()).append("'");
			if(ti.closable()){
				html.append(" closable='true'");
			}
			if(!ti.iconCls().isEmpty()){
				html.append(" data-settings=\"iconClassname:'").append(ti.iconCls()).append("'\"");
			}
			if(!ti.tools().isEmpty()){
				html.append(" tools='").append(ti.tools()).append("'");
			}
			if(StringHelper.isNotEmpty(ti.style())){
				html.append(" style='").append(ti.style()).append("'");
			}
			switch (ti.type()) {
			case VARIABLE:
				html.append(">$!{").append(ti.value()).append('}');
				break;
			case HREF:
				//这里需要把ID传过去
				html.append(" href='").append(path(ti.value())).append("?id=$!{form.id}'>");
				break;
			case INCLUDE:
				html.append(">#include(\"").append(ti.value()).append("\")");
				break;
			case PARSE:
				html.append(">#parse(\"").append(ti.value()).append("\")");
				break;
			case DETAIL:
				html.append('>');
				ObjectDescriptor detailDescriptor = DescriptorFactory.objectDescriptor(ti.clazz());
				StandardDetailBuilder dfb = new StandardDetailBuilder(ti.value(), detailDescriptor, this);
				dfb.parse();
				break;
			case CONTENT:
				html.append('>').append(ti.value());
				break;
			case GRID:
			default:
				html.append('>');
				
				AutoDataLoader loader = ti.loader();
				Class<?> resultClass = ti.clazz();
				//TODO 这里需要判断是treegrid还是datagrid
				Class<? extends Persistable> entityClass = loader.value();
				
				String gurl = null, val = ti.value();
				StandardGridBuilder qb;
				ObjectDescriptor od = DescriptorFactory.objectDescriptor(resultClass);
				AsTreeGrid treeGrid = od.getAnnotation(AsTreeGrid.class);
				if(null != treeGrid){
					qb = new TreegridBuilder(treeGrid, od, manager, html);
					if(!Persistable.class.equals(entityClass)){
						try {
							AbstractLoaderHandler autoHandler;
							if(od.isAssignable(AjaxNode.class)){
								autoHandler = new AjaxNodeLoaderHandler(manager, resultClass, loader);
							}else{
								autoHandler = new NodeLoaderHandler(manager, resultClass, loader);
							}
							gurl = _initLoader(qb, autoHandler, loader, val);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}else{
					AsDataGrid dataGrid = od.getAnnotation(AsDataGrid.class);
					Class<?> condition = null;
					if(null != dataGrid){
						if(dataGrid.pagination() != PaginationType.NONE){
							condition = dataGrid.condition();
							if(Object.class.equals(condition)){
								condition = PageCondition.class;
							}
						}else{
							condition = SearchCondition.class;
						}
						qb = new DatagridBuilder(dataGrid, od, manager, html);
					}else{
						condition = PageCondition.class;
						qb = new DatagridBuilder(od, manager, html);
					}

					if(!Persistable.class.equals(entityClass)){
						//表示使用了自动转值的设置
						try {
							ItemLoaderHandler autoHandler = new ItemLoaderHandler(manager, resultClass, condition, loader);

							gurl = _initLoader(qb, autoHandler, loader, val);
							
//							for(ConditionExpression ce : loader.queries()){
//								QueryParameter qp = autoHandler.addExpression(ce);
//								qb.addQueryParameter(qp);
//							}
//							QueryParameter qp = autoHandler.addExpression(val);
//							qb.addQueryParameter(qp);
//							manager.register(autoHandler);
//							gurl = autoHandler.getUrl();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				if(StringHelper.isEmpty(gurl)){
					gurl = val;
//				}else if(StringHelper.isNotEmpty(val)){
//					qb.addQueryParams(val, "jQuery('#" + this.id + "').val()");
				}
				qb.initLoader(gurl).parse();
				break;
			}
			html.append("</div>");
		}
	}

	private String _initLoader(StandardGridBuilder qb, AbstractLoaderHandler autoHandler, AutoDataLoader loader, String val) throws Exception {
		for(QueryExpression ce : loader.queries()){
			autoHandler.addExpression(ce);
		}
		autoHandler.addExpression(val, this.id);
		
		qb.addQueryParameters(autoHandler.getParameters());
		
		manager.register(autoHandler);
		return autoHandler.getUrl();
	}

	@Override
	public void js(String js) {
		html.includes("<script type='text/javascript' src='").append(path(js)).append("'></script>");
	}

	@Override
	public void css(String css) {
		html.includes("<link rel='stylesheet' href='").append(path(css)).append("' />");
	}

	@Override
	public String getBindedName() {
		return bindedName;
	}
}
